/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
 
This file is part of Quake III Arena source code.
 
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
 
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
//
#include "g_local.h"

#define	MISSILE_PRESTEP_TIME	50

/*
================
G_BounceMissile
 
================
*/
void G_BounceMissile( gentity_t *ent, trace_t *trace )
{
    vec3_t	velocity;
    float	dot;
    int		hitTime;

    // reflect the velocity on the trace plane
    hitTime = (int)(level.previousTime + ( level.time - level.previousTime ) * trace->fraction);
    BG_EvaluateTrajectoryDelta( &ent->s.pos, hitTime, velocity );
    dot = DotProduct( velocity, trace->plane.normal );
    VectorMA( velocity, -2*dot, trace->plane.normal, ent->s.pos.trDelta );

    if ( ent->s.eFlags & EF_BOUNCE_HALF )
    {
        VectorScale( ent->s.pos.trDelta, 0.65, ent->s.pos.trDelta );
        // check for stop
        if ( trace->plane.normal[2] > 0.2 && VectorLength( ent->s.pos.trDelta ) < 40 )
        {
            G_SetOrigin( ent, trace->endpos );
            return;
        }
    }

    VectorAdd( ent->r.currentOrigin, trace->plane.normal, ent->r.currentOrigin);
    VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase );
    ent->s.pos.trTime = level.time;
}


/*
================
G_ExplodeMissile
 
Explode a missile without an impact
================
*/
void G_ExplodeMissile( gentity_t *ent )
{
    vec3_t		dir;
    vec3_t		origin;

    BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );
    SnapVector( origin );
    G_SetOrigin( ent, origin );

    // we don't have a valid direction, so just point straight up
    dir[0] = dir[1] = 0;
    dir[2] = 1;

    ent->s.eType = ET_GENERAL;
    G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( dir ) );

    ent->freeAfterEvent = qtrue;

    // splash damage
    if ( ent->splashDamage )
    {
        if( G_RadiusDamage( ent->r.currentOrigin, ent->parent, ent->splashDamage, ent->splashRadius, ent
                            , ent->splashMethodOfDeath ) )
        {
            g_entities[ent->r.ownerNum].client->accuracy_hits++;
        }
    }

    SV_LinkEntity( ent );
}

/*
================
G_MissileImpact
================
*/
void G_MissileImpact( gentity_t *ent, trace_t *trace)
{
    gentity_t		*other;
    qboolean		hitClient = qfalse;
    other = &g_entities[trace->entityNum];

    if ((other->r.contents & CONTENTS_TRIGGER) && other->touch)
    {
        int hitTime = (int)(level.previousTime + ( level.time - level.previousTime ) * trace->fraction);

        BG_EvaluateTrajectoryDelta(&ent->s.pos, hitTime, ent->r.currentVelocity);
        BG_EvaluateTrajectory(&ent->s.pos, hitTime, ent->r.currentOrigin);

        other->touch(other, ent, trace);

        VectorCopy(ent->r.currentOrigin, ent->s.pos.trBase);
        VectorCopy(ent->r.currentVelocity, ent->s.pos.trDelta);
        ent->s.pos.trTime = hitTime;

        return;
    }

    // check for bounce
    if ( !other->takedamage &&
            ( ent->s.eFlags & ( EF_BOUNCE | EF_BOUNCE_HALF ) ) )
    {
        G_BounceMissile( ent, trace );
        G_AddEvent( ent, EV_GRENADE_BOUNCE, 0 );
        return;
    }

    // impact damage
    if (other->takedamage)
    {
        // FIXME: wrong damage direction?
        if ( ent->damage )
        {
            vec3_t	velocity;

            if( LogAccuracyHit( other, &g_entities[ent->r.ownerNum] ) )
            {
                g_entities[ent->r.ownerNum].client->accuracy_hits++;
                hitClient = qtrue;
            }
            BG_EvaluateTrajectoryDelta( &ent->s.pos, level.time, velocity );
            if ( VectorLength( velocity ) == 0 )
            {
                velocity[2] = 1;	// stepped on a grenade
            }
            G_Damage (other, ent, &g_entities[ent->r.ownerNum], velocity,
                      ent->s.origin, ent->damage,
                      0, ent->methodOfDeath);
        }
    }

    if (!strcmp(ent->classname, "hook"))
    {
        gentity_t *nent;
        vec3_t v;

        nent = G_Spawn();
        if ( other->takedamage && other->client )
        {

            G_AddEvent( nent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) );
            nent->s.otherEntityNum = other->s.number;

            ent->enemy = other;

            v[0] = other->r.currentOrigin[0] + (other->r.mins[0] + other->r.maxs[0]) * 0.5;
            v[1] = other->r.currentOrigin[1] + (other->r.mins[1] + other->r.maxs[1]) * 0.5;
            v[2] = other->r.currentOrigin[2] + (other->r.mins[2] + other->r.maxs[2]) * 0.5;

            SnapVectorTowards( v, ent->s.pos.trBase );	// save net bandwidth
        }
        else
        {
            VectorCopy(trace->endpos, v);
            G_AddEvent( nent, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) );
            ent->enemy = NULL;
        }

        SnapVectorTowards( v, ent->s.pos.trBase );	// save net bandwidth

        nent->freeAfterEvent = qtrue;
        // change over to a normal entity right at the point of impact
        nent->s.eType = ET_GENERAL;
        ent->s.eType = ET_GRAPPLE;

        G_SetOrigin( ent, v );
        G_SetOrigin( nent, v );

        ent->think = Weapon_HookThink;
        ent->nextthink = level.time + FRAMETIME;

        ent->parent->client->ps.pm_flags |= PMF_GRAPPLE_PULL;
        VectorCopy( ent->r.currentOrigin, ent->parent->client->ps.grapplePoint);

        SV_LinkEntity( ent );
        SV_LinkEntity( nent );

        return;
    }

    // is it cheaper in bandwidth to just remove this ent and create a new
    // one, rather than changing the missile into the explosion?

    if ( other->takedamage && other->client )
    {
        G_AddEvent( ent, EV_MISSILE_HIT, DirToByte( trace->plane.normal ) );
        ent->s.otherEntityNum = other->s.number;
    }
    else if( trace->surfaceFlags & SURF_METALSTEPS )
    {
        G_AddEvent( ent, EV_MISSILE_MISS_METAL, DirToByte( trace->plane.normal ) );
    }
    else
    {
        G_AddEvent( ent, EV_MISSILE_MISS, DirToByte( trace->plane.normal ) );
    }

    ent->freeAfterEvent = qtrue;

    // change over to a normal entity right at the point of impact
    ent->s.eType = ET_GENERAL;

    SnapVectorTowards( trace->endpos, ent->s.pos.trBase );	// save net bandwidth

    G_SetOrigin( ent, trace->endpos );

    // splash damage (doesn't apply to person directly hit)
    if ( ent->splashDamage )
    {
        if( G_RadiusDamage( trace->endpos, ent->parent, ent->splashDamage, ent->splashRadius,
                            other, ent->splashMethodOfDeath ) )
        {
            if( !hitClient )
            {
                g_entities[ent->r.ownerNum].client->accuracy_hits++;
            }
        }
    }

    SV_LinkEntity( ent );
}

/*
================
G_RunMissile
================
*/
void G_RunMissile( gentity_t *ent )
{
    vec3_t		origin;
    trace_t		tr;
    int			passent = (ent->target_ent) ? ent->target_ent->s.number : ent->r.ownerNum, mask = ent->clipmask | CONTENTS_TRIGGER;

    // get current position
    BG_EvaluateTrajectory( &ent->s.pos, level.time, origin );

    // trace a line from the previous position to the current position
    SV_Trace(&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, mask, qfalse);

    if(tr.fraction != 1)
    {
        gentity_t		*other = &g_entities[tr.entityNum];
        if(other->s.eType != ET_TELEPORT_TRIGGER)
        {
            mask = ent->clipmask;
            SV_Trace(&tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, origin, passent, mask, qfalse);
        }
    }

    if (tr.startsolid || tr.allsolid)
    {
        SV_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, passent, mask, qfalse);
        tr.fraction = 0;
    }
    else
        VectorCopy( tr.endpos, ent->r.currentOrigin );

    SV_LinkEntity(ent);

    if(tr.fraction != 1)
    {
        // never explode or bounce on sky
        if(tr.surfaceFlags & SURF_NOIMPACT)
        {
            // If grapple, reset owner
            if (ent->parent && ent->parent->client && ent->parent->client->hook == ent)
                ent->parent->client->hook = NULL;
            G_FreeEntity( ent );
            return;
        }

        G_MissileImpact(ent, &tr);

        if(ent->s.eType != ET_MISSILE)
            return;
    }

    // check think function after bouncing
    G_RunThink( ent );
}


//=============================================================================

/*
=================
fire_plasma
 
=================
*/
gentity_t *fire_plasma (gentity_t *self, vec3_t start, vec3_t dir)
{
    gentity_t	*bolt;

    VectorNormalize (dir);

    bolt = G_Spawn();
    bolt->classname = "plasma";
    bolt->nextthink = level.time + 10000;
    bolt->think = G_ExplodeMissile;
    bolt->s.eType = ET_MISSILE;
    bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    bolt->s.weapon = WP_RANGE;
    bolt->r.ownerNum = self->s.number;
    bolt->parent = self;
    bolt->damage = 20;
    bolt->splashDamage = 15;
    bolt->splashRadius = 20;
    bolt->methodOfDeath = MOD_PLASMA;
    bolt->splashMethodOfDeath = MOD_PLASMA_SPLASH;
    bolt->clipmask = MASK_SHOT;
    bolt->target_ent = NULL;

    bolt->s.pos.trType = TR_LINEAR;
    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;		// move a bit on the very first frame
    VectorCopy( start, bolt->s.pos.trBase );
    VectorScale( dir, 2000, bolt->s.pos.trDelta );
    SnapVector( bolt->s.pos.trDelta );			// save net bandwidth

    VectorCopy (start, bolt->r.currentOrigin);

    return bolt;
}

//=============================================================================


/*
=================
fire_grenade
=================
*/
gentity_t *fire_grenade (gentity_t *self, vec3_t start, vec3_t dir)
{
    gentity_t	*bolt;

    VectorNormalize (dir);

    bolt = G_Spawn();
    bolt->classname = "grenade";
    bolt->nextthink = level.time + 2500;
    bolt->think = G_ExplodeMissile;
    bolt->s.eType = ET_MISSILE;
    bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    bolt->s.weapon = WP_RANGE;
    bolt->s.eFlags = EF_BOUNCE_HALF;
    bolt->r.ownerNum = self->s.number;
    bolt->parent = self;
    bolt->damage = 100;
    bolt->splashDamage = 100;
    bolt->splashRadius = 150;
    bolt->methodOfDeath = MOD_GRENADE;
    bolt->splashMethodOfDeath = MOD_GRENADE_SPLASH;
    bolt->clipmask = MASK_SHOT;
    bolt->target_ent = NULL;

    bolt->s.pos.trType = TR_GRAVITY;
    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;		// move a bit on the very first frame
    VectorCopy( start, bolt->s.pos.trBase );
    VectorScale( dir, 700, bolt->s.pos.trDelta );
    SnapVector( bolt->s.pos.trDelta );			// save net bandwidth

    VectorCopy (start, bolt->r.currentOrigin);

    return bolt;
}

//=============================================================================


/*
=================
fire_bfg
=================
*/
gentity_t *fire_bfg (gentity_t *self, vec3_t start, vec3_t dir)
{
    gentity_t	*bolt;

    VectorNormalize (dir);

    bolt = G_Spawn();
    bolt->classname = "bfg";
    bolt->nextthink = level.time + 10000;
    bolt->think = G_ExplodeMissile;
    bolt->s.eType = ET_MISSILE;
    bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    bolt->s.weapon = WP_RANGE;
    bolt->r.ownerNum = self->s.number;
    bolt->parent = self;
    bolt->damage = 100;
    bolt->splashDamage = 100;
    bolt->splashRadius = 120;
    bolt->methodOfDeath = MOD_BFG;
    bolt->splashMethodOfDeath = MOD_BFG_SPLASH;
    bolt->clipmask = MASK_SHOT;
    bolt->target_ent = NULL;

    bolt->s.pos.trType = TR_LINEAR;
    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;		// move a bit on the very first frame
    VectorCopy( start, bolt->s.pos.trBase );
    VectorScale( dir, 2000, bolt->s.pos.trDelta );
    SnapVector( bolt->s.pos.trDelta );			// save net bandwidth
    VectorCopy (start, bolt->r.currentOrigin);

    return bolt;
}

//=============================================================================


/*
=================
fire_rocket
=================
*/
gentity_t *fire_rocket (gentity_t *self, vec3_t start, vec3_t dir)
{
    gentity_t	*bolt;

    VectorNormalize (dir);

    bolt = G_Spawn();
    bolt->classname = "rocket";
    bolt->nextthink = level.time + 15000;
    bolt->think = G_ExplodeMissile;
    bolt->s.eType = ET_MISSILE;
    bolt->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    bolt->s.weapon = WP_RANGE;
    bolt->r.ownerNum = self->s.number;
    bolt->parent = self;
    bolt->damage = 100;
    bolt->splashDamage = 100;
    bolt->splashRadius = 120;
    bolt->methodOfDeath = MOD_ROCKET;
    bolt->splashMethodOfDeath = MOD_ROCKET_SPLASH;
    bolt->clipmask = MASK_SHOT;
    bolt->target_ent = NULL;

    bolt->s.pos.trType = TR_LINEAR;
    bolt->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;		// move a bit on the very first frame
    VectorCopy( start, bolt->s.pos.trBase );
    VectorScale( dir, 900, bolt->s.pos.trDelta );
    SnapVector( bolt->s.pos.trDelta );			// save net bandwidth
    VectorCopy (start, bolt->r.currentOrigin);

    return bolt;
}

/*
=================
fire_grapple
=================
*/
gentity_t *fire_grapple (gentity_t *self, vec3_t start, vec3_t dir)
{
    gentity_t	*hook;

    VectorNormalize (dir);

    hook = G_Spawn();
    hook->classname = "hook";
    hook->nextthink = level.time + 10000;
    hook->think = Weapon_HookFree;
    hook->s.eType = ET_MISSILE;
    hook->r.svFlags = SVF_USE_CURRENT_ORIGIN;
    hook->s.weapon = WP_RANGE;
    hook->r.ownerNum = self->s.number;
    hook->methodOfDeath = MOD_GRAPPLE;
    hook->clipmask = MASK_SHOT;
    hook->parent = self;
    hook->target_ent = NULL;

    hook->s.pos.trType = TR_LINEAR;
    hook->s.pos.trTime = level.time - MISSILE_PRESTEP_TIME;		// move a bit on the very first frame
    hook->s.otherEntityNum = self->s.number; // use to match beam in client
    VectorCopy( start, hook->s.pos.trBase );
    VectorScale( dir, 800, hook->s.pos.trDelta );
    SnapVector( hook->s.pos.trDelta );			// save net bandwidth
    VectorCopy (start, hook->r.currentOrigin);

    self->client->hook = hook;

    return hook;
}
